"""
Basic monitoring objects.
"""



from twisted.python.util import mergeFunctionMetadata



class MonitoredDescriptor(object):
    """
    A monitored descriptor: wrap a class attribute to add set
    notifications.
    """

    def __init__(self, attributeName):
        self.attributeName = attributeName
        self.value = None


    def __get__(self, oself, type=None):
        """
        Upon get, return stored value.
        """
        assert oself is not None
        return self.value


    def __set__(self, oself, value):
        """
        Upon set, forward notification to the agent L{attributeSetNotification}
        method.
        """
        agent = getattr(oself, '__agent__', None)
        if agent is not None:
            agent.attributeSetNotification(oself, self.attributeName, value)
        self.value = value



def monitoredMethod(meth):
    """
    Method decorator that send a notification using L{methodCallNotification},
    recording arguments and timing of the call.
    """
    def f(*args, **kwargs):
        agent = None
        if args:
            agent = getattr(args[0], '__agent__', None)
        try:
            t = agent.seconds()
            return meth(*args, **kwargs)
        finally:
            if agent is not None:
                agent.methodCallNotification(args[0], meth.__name__, args[1:], kwargs, agent.seconds() - t)
    return mergeFunctionMetadata(meth, f)



class MonitoredClass(object):
    """
    Helper class to ease the creation of classes with monitored attributes and
    methods.

    @cvar monitoredAttributes: list of attributes to monitor.
    @type monitoredAttributes: C{list} of C{str}

    @cvar monitoredMethods: list of methods to monitor.
    @type monitoredMethods: C{list} of C{str}
    """

    class __metaclass__(type):
        def __new__(cls, name, bases, attrs):
            if 'monitoredAttributes' in attrs:
                monitoredAttributes = attrs.pop('monitoredAttributes')
                for i in monitoredAttributes:
                    attrs[i] = MonitoredDescriptor(i)
            if 'monitoredMethods' in attrs:
                monitoredMethods = attrs.pop('monitoredMethods')
                for i in monitoredMethods:
                    if i in attrs:
                        attrs[i] = monitoredMethod(attrs[i])
            subcls = type.__new__(cls, name, bases, attrs)
            return subcls


    def __init__(self, agent):
        self.__agent__ = agent
